package com.baidu.bjf.remoting.protobuf;

import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
import com.baidu.bjf.remoting.protobuf.utils.StringUtils;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.*;

public class MessageGenerator {

    public Collection<Class<?>> generate(Collection<Class<?>> classes, String projectPath) throws Exception {
        String codecPath = Paths.get(projectPath,"codec").toString();
        String protoPath = Paths.get(projectPath,"proto").toString();
        System.out.println("codec目录:" + codecPath);
        System.out.println("proto目录" + protoPath);

        classes = topologicalSort(classes);
        Set<Class<?>> cachedTypes = new HashSet<>();
        Set<Class<?>> cachedEnumTypes = new HashSet<>();
        StringBuilder sb = new StringBuilder();
        for (Class<?> clazz : classes) {
            String fullClassName = ProtobufProxy.getFullClassName(clazz);
            FileOutputStream fos = createFOS(codecPath, fullClassName);
            ProtobufProxy.dynamicCodeGenerate(fos, clazz, StandardCharsets.UTF_8);
//            System.out.println("生成服务端codec:" + fullClassName);
            //IDL 是 Interface Definition Language 的缩写，中文意思可以理解为 接口定义语言。
            String protoContent = ProtobufIDLGenerator.getIDL(clazz, cachedTypes, cachedEnumTypes);
            if (protoContent != null) {
                String outDir = ProtobufIDLGenerator.getOutDir(clazz);
                String protoFileName = clazz.getSimpleName() + ".proto";
                String targetDir = protoPath + File.separator + outDir;
                File f = new File(targetDir);
                if (!f.exists()) {
                    f.mkdirs();
                }
                writeProtoFile(targetDir, protoFileName, protoContent);
//                System.out.println("生成proto文件:" + fullClassName);
                sb.append(String.format("%s", protoPath + File.separator + protoFileName)).append("\r\n");
            }
        }
//        File generate_order = new File(protoPath + File.separator + "generate_order.txt");
//        if (!generate_order.exists()) {
//            generate_order.createNewFile();
//        }
//        FileUtils.writeStringToFile(generate_order, sb.toString(), Charset.forName("UTF8"));

        return classes;
    }

    private void writeProtoFile(String basePath, String fileName, String content) throws IOException {
        File dir = new File(basePath);
        if (!dir.exists() && !dir.mkdirs()) {
            throw new IOException("无法创建目录: " + basePath);
        }

        File file = new File(dir, fileName);
        try (FileWriter writer = new FileWriter(file)) {
            writer.write(content);
        }
    }

    private FileOutputStream createFOS(String pathStr, String className) {
        File path = new File(pathStr);
        if (path != null && path.isDirectory()) {
            String pkg = "";
            if (className.indexOf('.') != -1) {
                pkg = StringUtils.substringBeforeLast(className, ".");
            }
            // mkdirs
            String dir = path + File.separator + pkg.replace('.', File.separatorChar);
            File f = new File(dir);
            f.mkdirs();

            String simpleName = className.substring(className.lastIndexOf(".") + 1);
            try {
                return new FileOutputStream(new File(f, simpleName + ".java"));
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        throw new IllegalStateException("error");
    }

    /**
     * 返回拓扑排序后的 Class 列表（被依赖的优先） Kahn 算法
     */
    private static List<Class<?>> topologicalSort(Collection<Class<?>> resourceClasses) {
        Map<Class<?>, Set<Class<?>>> graph = new HashMap<>();
        Map<Class<?>, Integer> indegree = new HashMap<>();

        for (Class<?> clazz : resourceClasses) {
            Set<Class<?>> dependencies = findDependencies(clazz);

            graph.computeIfAbsent(clazz, k -> new HashSet<>());
            indegree.putIfAbsent(clazz, 0);

            for (Class<?> dep : dependencies) {
                graph.computeIfAbsent(dep, k -> new HashSet<>()).add(clazz);
                Integer old = indegree.getOrDefault(clazz, 0);
                indegree.put(clazz, old + 1);
                indegree.putIfAbsent(dep, 0);
            }
        }

        // 拓扑排序：Kahn 算法
        Queue<Class<?>> queue = new LinkedList<>();
        for (Class<?> clazz : indegree.keySet()) {
            if (indegree.get(clazz) == 0) {
                queue.add(clazz);
            }
        }

        List<Class<?>> sorted = new ArrayList<>();
        while (!queue.isEmpty()) {
            Class<?> current = queue.poll();
            sorted.add(current);
            for (Class<?> neighbor : graph.getOrDefault(current, Collections.emptySet())) {
                indegree.put(neighbor, indegree.get(neighbor) - 1);
                if (indegree.get(neighbor) == 0) {
                    queue.add(neighbor);
                }
            }
        }

        if (sorted.size() != resourceClasses.size()) {
            throw new RuntimeException("配置存在循环依赖，无法拓扑排序！");
        }

        return sorted;
    }

    private static Set<Class<?>> findDependencies(Class<?> clazz) {
        Set<Class<?>> deps = new HashSet<>();
        for (Field field : clazz.getDeclaredFields()) {
            Class<?> type = field.getType();
            // 1. 普通对象依赖
            if (type.isAnnotationPresent(ProtobufClass.class)) {
                deps.add(type);
                continue;
            }

            // List<T> 或 Set<T> 依赖
            if (List.class.isAssignableFrom(type) || Set.class.isAssignableFrom(type)) {
                Type genericType = field.getGenericType();
                if (genericType instanceof ParameterizedType) {
                    Type actualType = ((ParameterizedType) genericType).getActualTypeArguments()[0];
                    if (actualType instanceof Class) {
                        Class<?> genericClazz = (Class<?>) actualType;
                        if (genericClazz.isAnnotationPresent(ProtobufClass.class)) {
                            deps.add(genericClazz);
                        }
                    }
                }
            }

            // 3. Map<K,V> 依赖
            if (Map.class.isAssignableFrom(type)) {
                Type genericType = field.getGenericType();
                if (genericType instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType) genericType;
                    Type keyType = pt.getActualTypeArguments()[0];
                    Type valueType = pt.getActualTypeArguments()[1];

                    if (keyType instanceof Class) {
                        Class<?> keyClazz = (Class<?>) keyType;
                        if (keyClazz.isAnnotationPresent(ProtobufClass.class)) {
                            deps.add(keyClazz);
                        }
                    }
                    if (valueType instanceof Class) {
                        Class<?> valueClazz = (Class<?>) valueType;
                        if (valueClazz.isAnnotationPresent(ProtobufClass.class)) {
                            deps.add(valueClazz);
                        }
                    }
                }
            }
        }
        return deps;
    }


}
